package chiselTests.util.experimental

import chisel3._
import chisel3.simulator.scalatest.ChiselSim
import chisel3.simulator.stimulus.RunUntilFinished
import chisel3.util.BitPat
import chisel3.util.experimental.decode._
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

class DecoderTableSpec extends AnyFlatSpec with Matchers with ChiselSim {
  /*
   * Assuming a simple ALU instruction scheme
   *      | Name Width Register
   * addb | 0 0 XX
   * addw | 0 1 XX
   * subb | 1 0 XX
   * subw | 1 1 XX
   *
   * Then the operator type and width can be generated by its name
   */
  object Op {
    val add = "0"
    val sub = "1"
  }

  case class Insn(val op: String, val wide: Boolean) extends DecodePattern {
    override def bitPat: BitPat = BitPat("b" + op + (if (wide) "1" else "0") + "??")
  }

  object IsWideOp extends BoolDecodeField[Insn] {
    override def name = "iswide"

    override def genTable(i: Insn): BitPat = {
      if (i.wide) y else n
    }
  }

  object IsAddOp extends BoolDecodeField[Insn] {
    override def name = "isadd"

    override def genTable(i: Insn): BitPat = {
      if (i.op == Op.add) y else n
    }
  }

  object legal extends BoolDecodeField[Insn] {
    override def name = "legal"

    // default decode to n rather than don't care, for some special elements
    override def default: BitPat = n

    override def genTable(i: Insn): BitPat = y
  }

  class ExampleALUDecoder extends Module {
    val inst = IO(Input(UInt(4.W)))
    val isWideOp = IO(Output(Bool()))
    val isAddOp = IO(Output(Bool()))
    val isLegal = IO(Output(Bool()))

    val allInstructions = Seq(
      Insn(Op.add, true),
      Insn(Op.add, false),
      Insn(Op.sub, true),
      Insn(Op.sub, false)
    )

    val decodeTable = new DecodeTable(allInstructions, Seq(IsWideOp, IsAddOp, legal))
    val decodedBundle = decodeTable.decode(inst)
    isWideOp := decodedBundle(IsWideOp)
    isAddOp := decodedBundle(IsAddOp)
    isLegal := decodedBundle(legal)
  }

  "DecoderTable" should "elaborate a decoder" in {
    circt.stage.ChiselStage.emitCHIRRTL(new ExampleALUDecoder)
  }

  "DecoderTable" should "decode every field" in {
    simulate(new Module {
      val input = "b1100".U(4.W)
      val dut = Module(new ExampleALUDecoder)
      dut.inst := input
      chisel3.assert(dut.isWideOp, "WRONG OUTPUT %b", dut.isWideOp)
      stop()
    })(RunUntilFinished(3))
  }
}
